﻿using System;
using System.Collections.Generic;
using UnityEngine;

namespace HKTools
{
    [Serializable]
    public abstract class BaseHKEnum
    {
        public const string kValuePropName = "_value";
        public const string kIndexPropName = nameof(_index);
        public const string kProxyPropName = nameof(_proxy);

        [SerializeField]
        protected string _proxy;
        [SerializeField]
        protected int _index;

        public abstract object valueObject { get; }
    }

    [Serializable]
    public abstract class HKEnum<T> : BaseHKEnum, IEquatable<HKEnum<T>>, ISerializationCallbackReceiver
                                where T : Enum
    {
        [SerializeField]
        protected T _value;

        public T value
        {
            get => _value;
            set => _value = value;
        }

        public override object valueObject => _value;

        public void OnBeforeSerialize()
        {
            _proxy = _value.ToName();
            _index = (int)(ValueType)_value;
        }

        public void OnAfterDeserialize()
        {
            if (!HKEnumHelper.TryParse(_proxy, out _value))
            {
                if (Enum.IsDefined(typeof(T), _index))
                {
                    _value = (T)(ValueType)_index;
                }
                else
                {
                    Debug.LogErrorFormat("Deserialization failed: \"{0}\" enum has neither \"{1}\" value, nor \"{2}\" index", typeof(T), _proxy, _index);
                }
            }
        }

        public bool Equals(HKEnum<T> other) => EqualityComparer<T>.Default.Equals(_value, other.value);
        public override bool Equals(object obj) => Equals(obj as HKEnum<T>);
        public override int GetHashCode() => _value.GetHashCode();
        public static implicit operator T(HKEnum<T> stableEnum) => stableEnum._value;
        public static T Convert(HKEnum<T> stableEnum) => stableEnum._value;
    }

    internal static class HKEnumHelper
    {
        public static string ToName<T>(this T element) where T : Enum
        {
            return Enum.GetName(typeof(T), element);
        }

        public static bool TryParse<T>(string value, out T ret) where T : Enum
        {
            try
            {
                ret = (T)Enum.Parse(typeof(T), value);
                return true;
            }
            catch
            {
                ret = default(T);
                return false;
            }
        }
    }
}